0
votes

I am very new in React-redux-hooks. My objective is to bring the data from inputView.js into temperatureView.js when I click button.

Here is some of my code,

I initialize useSelector and useDispatch inside inputView.js

const temperatureSelector=useSelector(state=>state.temperatureInfo)
const dispatch = useDispatch();

...

const handleClick = (e) => {
  e.preventDefault();
  dispatch(getTemperature());  
  history.push({
    pathname: '/temperatureView',
    state: { params: temperatureSelector }
  })
}

When I log inside temperatureView.js, it gives nothing. Am I using the useSelector wrongly? or maybe useDispatch? Or should I dispatch inside useEffect?

How to do this?

EDIT

actions/index.js

export const getTemperature = (city) => {
return async function(dispatch) {
    await fetch("myhttp").then(res => {
        return res.json();
    }).then(resJOSN => {
        dispatch({type: "GET_TEMPERATURE", payload: resJOSN})
    }).catch(err => {
        console.log(err);
    })
    
}
}

reducers/index.js

import { combineReducers } from 'redux';
const temperatureinfo = (state = {
temperatureinfo:{}
}, action) => {
if(action.type === "GET_TEMPERATURE") {
    state = {...state, temperatureInfo:action.payload}
}
return state
}
const reducers = combineReducers ({
temperatureInfo:temperatureinfo,
})
export default reducers;

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './view/App';
import reportWebVitals from './reportWebVitals';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import reducers from './reducers';
import thunk from 'redux-thunk';

ReactDOM.render(
  <Provider store={createStore(reducers, applyMiddleware(thunk))}><App /></Provider>,
    
  document.getElementById('root')
);

reportWebVitals();

App.js

import TemperatureView from './TemperatureView';
import InputView from './InputView';

function App(props) {

  return (
    <div>
      <BrowserRouter>
        <Route path="/" exact component={InputView} />
        <Route path="/temperatureView" component={TemperatureView}/>
      </BrowserRouter>
    </div>
  );
}

export default App;

InputView.js

    import React, {useEffect} from 'react';
import { makeStyles } from '@material-ui/core/styles';

import InputLabel from '@material-ui/core/InputLabel';
import MenuItem from '@material-ui/core/MenuItem';
import Select from '@material-ui/core/Select';
import FormControl from '@material-ui/core/FormControl';
import Input from '@material-ui/core/Input';
import Button from '@material-ui/core/Button';
import { useHistory } from "react-router-dom";
import { useSelector, useDispatch } from 'react-redux';
import { getTemperature } from '../actions';

const useStyles = makeStyles((theme) => ({
  root: {
    '& > *': {
      margin: theme.spacing(1),
    },
  },
}));

function InputView(props) {

  const history = useHistory();
  const classes = useStyles();
  const City =  [
    {"id": "YG", "name": 'Yangon'},
    {"id": "NY", "name": 'New York'}
  ];

  const [state, setState] = React.useState({
    apiKey: '123',
    cityName: 'NY'
  });

   const temperatureSelector=useSelector(state=>state.temperatureInfo)
   const dispatch = useDispatch();

  const handleChange = (event) => {
    const name = event.target.name;
    setState({
      ...state,
      [name]: event.target.value,
    });
  };

  const handleChange1 = (event) => {
    const name = event.target.name;
    setState({
      ...state,
      [name]: event.target.value,
    });
  };
  
  const handleClick = (e) => {
    e.preventDefault();
    dispatch(getTemperature());  
    history.push({
      pathname: '/temperatureView',
      state: { params: temperatureSelector }
    })
    
  }

  useEffect(()=>{
    console.log('mounted');
    return () => console.log('unmounting...');
    },[]
  );

  return (
    <div style={{display: "flex", height:"100vh", alignItems:"center", justifyContent:"center" }}> 
        <form className={classes.root} noValidate autoComplete="off">
            <div style={{marginTop:"40px", marginBottom:"40px"}}>
                <FormControl>
                    <InputLabel htmlFor="input-form" style={{color:"red"}}>Your API Key</InputLabel>
                    <Input id="input-form" value={state.apiKey} name="apiKey" onChange={handleChange} style={{width:"300px"}} />
                </FormControl>
            </div>
            <div style={{marginTop:"40px", marginBottom:"40px"}}>
                <FormControl className={classes.formControl}>
                    <InputLabel shrink id="button-file" style={{color:"red"}}>
                    City Name
                    </InputLabel>
                    <Select
                        labelId="button-file"
                        id="button-file"
                        value = {state.cityName}
                        displayEmpty
                        name="cityName"
                        onChange={handleChange1}
                        style={{width:"300px"}}
                    >
                        <MenuItem key={City[0].id} value={City[0].id}>
                        {City[0].name}
                        </MenuItem>
                        <MenuItem key={City[1].id} value={City[1].id}>
                        {City[1].name}
                        </MenuItem>
                    </Select>
            </FormControl>
            </div>
        <div style={{marginTop:"100px", marginBottom:"100px"}}>
        <Button type="submit" htmlFor="button-file" variant="contained" color="secondary" fullWidth={true} onClick={handleClick} >Submit</Button>
        </div>
      </form>
    </div>
  );
}

export default InputView;

TemperatureView.js

    import React from 'react';
import Typography from '@material-ui/core/Typography';
import TextField from '@material-ui/core/TextField';

function TemperatureView(props) {
    return <div style={{marginTop: "80px", marginLeft: "40px"}}> 
        <Typography color="error" >
            Celcius
        </Typography>
        <TextField
          id="celcius"
          defaultValue="23.5"
          InputProps={{
            readOnly: true,
          }}
        />
        <Typography color="error" >
            Fahrenheit
        </Typography>
        <TextField
          id="fahrenheit"
          defaultValue="2234"
          InputProps={{
            readOnly: true,
          }}
        />
    </div>
}

export default TemperatureView;
2
I think need more information about ur reducer and actions. - Won Gyo Seo
Depends. Are you trying to dispatch to get the temperature when the component mounts (for use later, i.e. when the usr clicks on something)? Or rather, it seems you are trying to dispatch an action to get the temperature and then immediately navigate away from the current page before the temperatureSelector value has updated, storing whatever the current value is in route state. - Drew Reese
Can you update your question to include a Minimal, Complete, and Reproducible code example of this component and the component where you are attempting to access the route state? Can you talk thought what you are wanting and expecting the code to do so we can understand your use case and provide better assistance? - Drew Reese
@DrewReese I already update my question with all the code. my objective is I want to use later, after the user click the button. An I want to display on the next page. - Safiah Nadiah
@WonGyoSeo I updated with codes. - Safiah Nadiah

2 Answers

1
votes

Issues

  1. You don't dispatch a city to get a temperature of.
  2. You immediately navigate to "/temperatureView" while the just-dispatched action is still processing, so temperatureSelector has yet to update in state.

Solution 0 - Dispatch action that fetches temp and navigates when complete

Create a global history object that you can dispatch navigation actions from your asynchronous actions. When the form is submitted just dispatch the action. When the fetch resolves a navigation to the target path is done and the app navigates to the page with the route state payload.

App.js

Instead of BrowserRouter use a regular Router so you can use your own history object. Create a history object from the history package.

import { Router } from 'react-router-dom';
import { createBrowserHistory } from 'history';
...

export const history = createBrowserHistory()

function App(props) {

  return (
    <div>
      <Router history={history}>
        ...
      </Router>
    </div>
  );
}

action

fetch returns a Promise and you are already chaining from it, so there is no need to declare the function async and await the fetch resolution. When the fetch resolves and navigation can occur. Pass the response object in route state.

import { history } from '/path/to/app.js';

export const getTemperature = (city) => {
  return function(dispatch) {
    fetch("myhttp")
      .then(res => res.json())
      .then(resJOSN => {
        dispatch({ type: "GET_TEMPERATURE", payload: resJOSN });
        history.push({
          pathname: '/temperatureView',
          state: {
            temperatureSelector: resJOSN,
          },
        });
      })
      .catch(err => {
        console.log(err);
      });
  }
}

InputView.js

Update the handler to pass state.cityName to getTemperature action.

const handleClick = (e) => {
  e.preventDefault(); // <-- prevent default form action
  dispatch(getTemperature(state.cityName)); // <-- pass city
}

Fix the form submit button. It is already within the form so no onClick handler is necessary. The handler should be attached to the form's onSubmit handler.

return (
  ...
    <form
      className={classes.root}
      noValidate autoComplete="off"
      onSubmit={handleClick}
    >
      ...    
    
      <Button
        type="submit" // <-- will submit the form when clicked!
        variant="contained"
        color="secondary"
        fullWidth={true}
      >
        Submit
      </Button>
    </form>
  ...
);

TemperatureView.js

Access the route state using the useLocation hook.

import React from 'react';
import { useLocation } from 'react-router-dom';
...

function TemperatureView(props) {
  const { state: { temperatureSelector } } = useLocation(); // <-- access route state

  return (
    ...
  );
}

Solution 1 - Dispatch action to fetch temp and immediately navigate

Dispatch action to fetch the city temperature and immediately navigate to new page. That page is subscribed to your redux store and "waits" for state to update.

InputView.js

Same updates from above, but also navigate. Remove the temperatureSelector line, this will be accessed elsewhere.

const handleClick = (e) => {
  e.preventDefault(); // <-- prevent default form action
  dispatch(getTemperature(state.cityName)); // <-- pass city
  history.push('/temperatureView'); // <-- navigate immediately
}

action

Same changes as above, but no history import and no navigation action.

export const getTemperature = (city) => {
  return function(dispatch) {
    fetch("myhttp")
      .then(res => res.json())
      .then(resJOSN => {
        dispatch({ type: "GET_TEMPERATURE", payload: resJOSN });
      })
      .catch(err => {
        console.log(err);
      });
  }
}

TemperatureView.js

Access the redux state using the useSelector hook. Remember to do any conditional rendering (i.e. "...loading") while the city temperature is being fetched and populated in state.

import React from 'react';
...

function TemperatureView(props) {
  const temperatureSelector = useSelector(state => state.temperatureInfo);

  return (
    ...
  );
}
0
votes

Put your actions inside useEffect , you have to trigger the actions

 useEffect(() => {
   dispatch(getTemperature());  
  }, []);