Updating state from a function


#1

hello all i have a function thats called upon a websocket join, and id like to update the component state to show the room number connected to. i tried emiting an action and using ReasonReact.Update( but to no avail, can someone tell me where ive gone wrong
heres the code, i have a state and app component i hold my types and funs here then import to the app fun by open State, i can see the output in console log when i send messages down the websocket from my server.

//state.re
[@bs.module “phoenix”]

type state = {
status: string,
};

open Phx

type action =
| Add

let handleEvent = (event, response) =>
switch event {
| “test” =>
Js.log((“handleEvent:” ++ event, response))
| _ => Js.log((“handleReiceive:” ++ event))
};
let socket = initSocket("/socket") |> connectSocket |> putOnClose(() => Js.log(“Socket closed”));

let join = (channel) =>
{

let _ =
channel
|> putOn(“test”, handleEvent(“test”))
|> putOnSyncState(handleSyncState)
|> putOnsyncDiff(handleSyncDiff)
|> joinChannel
|> putReceive(“ok”, handleReiceive(“ok”))
|> putReceive(“error”, handleReiceive(“error”))
};

let channel = socket |> initChannel(“lobby”);

join(channel);

let reducer = (action, state) =>
switch(action) {
| Add=> ReasonReact.Update({…state, status: “testing”})
};

///app.re
open State;

let component = ReasonReact.reducerComponent(“App”);
let make = _children => {
…component,
initialState: () => {
status: “string”,
},
reducer,
render: ({state, send}) =>



(ReasonReact.string(“the title”))

,
};

#2

Hi! I don’t see you actually sending Add from anywhere. You have your component, you have your action type, your reducer should update state on the Add action, but you don’t have send(Add) anywhere (like in an onClick handler or smth). See, for instance, the starter app (notice self.send):

  reducer: (action, state) =>
    switch (action) {
    | Click => ReasonReact.Update({...state, count: state.count + 1})
    | Toggle => ReasonReact.Update({...state, show: ! state.show})
    },

  render: self => {
    let message =
      "You've clicked this " ++ string_of_int(self.state.count) ++ " times(s)";
    <div>
      <button onClick=(_event => self.send(Click))>
        (ReasonReact.string(message))
      </button>
      <button onClick=(_event => self.send(Toggle))>
        (ReasonReact.string("Toggle greeting"))
      </button>
      (self.state.show ? ReasonReact.string(greeting) : ReasonReact.null)
    </div>;
  },

As for the whole channel thing, I’m not sure how it’s supposed to work, but it happens totally outside of the component’s lifecycle. Maybe you should run the whole thing inside didMount.

Also, I’d recommend using markdown code blocks for your code snippets. Makes it way easier to read.

UP: I’ve reread your question, namely, the part about the function you want to use. But the problem is you have to use send, which is passed as an argument to render, so if you use some external function, you’d have to somehow pass send from inside render to that function. Which, by the way, would mean you cannot send anything before your component is mounted and render is called. So, if you send some events after your socket opens or something, you have to wait for both the render call and your socket events. Which makes your code more complicated than it should be.

Another problem with opening sockets right inside the module is that you do it in the hot path, not waiting until your app initializes, your components mount etc.

So, I still think it’s better to go with the React flow and init your socket inside a lifecycle method.


#3

I now init my websocket in the lifecycle method but what about where the handelevent method is called i cant put that in my make, my flow is i need to init the socket in the didMount ifecycle, then after i hit the send button i can jouin the channel which the runs the join fun but i dont know how/where to put the handleevent fun where send is available, its basiucally a callback when the channel object is updated.


#4

i found this medium article https://medium.com/coding-artist/full-stack-react-with-phoenix-chapter-9-channels-245a24647e84 and it shoes the socket being loaded in the constructor, im guessing thats the make function in reason, then storing the channel in this, which is self.state i guess. how would i go about inspecting what the channel object looks like so i know what type it is, or is the article even feasible in reason? it also shows using setstate from the didmount lifecycle can i do this in reason?


#5

I’m not sure if this answers your question but you can pass in send like this:

let make = (~foo, _children) => {
  let handleClick = (send, event) => {
    /* foo is in scope here as well*/
  };

  {
    ...component,
    render: _self =>
      <div onClick=handleClick(send)>
      </div>
  }
};

I haven’t tested that snippet but i’ve used a pattern like that before. You can also use this guide to pass self around in a handler: https://reasonml.github.io/reason-react/docs/en/callback-handlers


#6

Thx that answers my questions want sure if i could use a callback in the make command you’re awesome


#7

The handlers like that should be used with self.handle since it’s technically reading in to self. It tripped me up too when I thought I was being clever for form changes.

See here for details on it.

let make = (~foo, _children) => {
  let handleClick = (event, self) => {
    self.ReasonReact.send(someCallback(foo))
    /* foo is in scope here as well*/
  };

  {
    ...component,
    render: _self =>
      <div onClick={self.handle(handleClick)}>
      </div>
  }
};

Edit: The risks of NOT using the self.handle is that you might be acting on a stale state. A lot of the time, you’ll never notice it, but there is always that one time.


#8

ahhh good to know!


#9

I’ve now tried this for days and still cannot get it to work, it seems simple but it’s not ( prolly really is) i need to connect to the server get the channel name then join the channel, so far i connectt to server get channel name but in stuck there, l can see the messages coming in via the log but they don’t update the component, when i put the Handel in the make fun i got out of scope errors


#10

Is your ReasonReact.Update getting executed? I would add a log right before this to double check the action is getting sent. The UI should change after this gets fired.

Your out of scope error sounds like this one (the link to this is kind of hidden on the page I linked to before)


#11

ive moldified my module but still nothing heres my code, it keeps telling me i need to put a ; somewhere after |> puton, but i think its really something to do with not funding self, in this example i need to send to ADDQUOTE in order to rerender there is a quotes container thats not included because it errors, and this is the only way i could get to compile, as data comes in from the socket and hits puton i need to update the quotes array and update the quotes container

[@bs.module "phoenix"]

open Phx

type quote = {
  id: int,
  company: string,
  profile: string,
  price: int
};


type tQuotes = array(quote)

type state = {
searchType: string,
quotes: tQuotes
}

type action =
| JOINCHANNEL
| ADDQUOTE(quote)

 let reducer = (action, state) =>
 switch(action) {
 | JOINCHANNEL =>  ReasonReact.Update({...state, searchType: "ZIP"})
 | ADDQUOTE(quote) =>  ReasonReact.Update({...state, quotes: Array.append(state.quotes, [|quote|])})
 }

let component = ReasonReact.reducerComponent("Search");

let make = (~websocket_id: string, _children) => {

    let handleEvent = (event, response) => {
      let _ = Js.log(("handleEvent:" ++ event, response));
       ();
    };

let socket = initSocket("/socket")
|> connectSocket
|> putOnClose(() => Js.log("Socket closed"))
|> initChannel(websocket_id)
|> joinChannel
|> putOn "from_server" (handleEvent "from:server")

;


{
  ...component,
   initialState: () => {searchType: "ZIP", quotes: [||]},
  reducer,
  render: ({state, send}) =>

<Footer />

  }

  };

im opening phx to expose those methods but phx are ml files and i font know enough about ocaml to understand how the reply comes in and to handel it, maybe it is not possilbe can someone that has more expertiese in ocaml give me some advice as to where im going wrong or at least point me to a resource to i can understand more about how to interpet this logic


#12

i figured this out if anyone needs this the problem iwas having was because the type was wrong it was i fixed it like

didMount: (self) =>
{
let socket = initSocket("/socket")
|> connectSocket
|> putOnClose(() => Js.log(“Socket closed”))

let quote_channel = “quote:” ++ websocket_id

let channel = socket
    |> initChannel(quote_channel)
    |> putOn("quote", handleEvent(self, "quote"))
    |> joinChannel
},

then above the component declaration

module Decode = {
    let quote = (json) => Json.Decode.{
        id: json |> field("id", int),
        }
     };

let handleEvent = (handle, event, response) => {

let decoded =  response |> Decode.quote ;


let quote = {
             id: decoded.id
            };

  handle.ReasonReact.send(ADDQUOTE(quote));
  ();
};