i want to make a counter Component when i learning in react and redux now.
so i want to change the eval input and then i will click the resolve input,and the end show the resolve to component2.but when i clicked,the resolve was changed in reducer and actions (i was consoled the resolve) , but had not change in component2,why?my English is not good,thanks...
this is my full code:
actions.js
export function DO_COUNT(resolve) {
return {
type: 'DO_COUNT',
payload: resolve
}
}
reducer.js
import actions from '../actions'
export default (state = { resolve: 0 }, actions) => {
switch (actions.type) {
case 'DO_COUNT':
console.log({
...state,
resolve: actions.payload
})
return {
...state,
resolve: actions.payload
}
break
default:
return state
}
}
store.js
import { createStore } from 'redux'
import reducers from '../reducers'
import { composeWithDevTools } from 'redux-devtools-extension'
const store = createStore(reducers, composeWithDevTools())
export default store
my component1:Counter.js
import React, { Component } from 'react'
import styleObj from './style.less'
import store from '../../store'
import { DO_COUNT, CHANGE_EVAL } from '../../actions'
export default class Counter extends Component {
constructor(props) {
super(props)
this.state = {
num1: 0,
num2: 0,
myEval: '+'
}
}
changeEval = e => {
this.setState({
myEval: e.target.value
})
}
changeNum1 = e => {
// let _target = e.target.dataset.target
let value = e.target.value
this.setState(
{
num1: value
},
() => {
console.log(this.state)
}
)
}
changeNum2 = e => {
// let _target = e.target.dataset.target
let value = e.target.value
this.setState(
{
num2: value
},
() => {
console.log(this.state)
}
)
}
doCount = () => {
let resolve = eval(
[this.state.num1, this.state.num2].join(this.state.myEval)
)
store.dispatch(DO_COUNT(resolve))
}
render() {
return (
<div className={styleObj.counterBox}>
<input type="number" onInput={this.changeNum1} data-target="num1" />
<select onChange={this.changeEval}>
<option defaultValue="+">+</option>
<option value="-">-</option>
<option value="*">*</option>
<option value="/">/</option>
</select>
<input type="number" onInput={this.changeNum2} data-target="num2" />
<input type="button" value="=" onClick={this.doCount} />
</div>
)
}
}
my component2:Container.js
import React, { Component } from 'react'
import styleObj from './style.less'
import store from '../../store'
export default class Container extends Component {
constructor(props) {
super(props)
}
render() {
return <h1 className={styleObj.content}>{store.getState().resolve}</h1>
}
}
and image:
You should be using react-redux.
The problem is your Container component is not being notified when the store's state changes. You can do this manually by hooking into lifecycle methods and setting state, but this is what react-redux already does (in a more optimized way).
Related
How to pass text value to another component using Redux in React?
I am learning Redux in React. I am trying to pass text value to another component using Redux in React.
My code is like below
Mycomponent.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
class Mycomponent extends Component {
state = {
textInput: '',
}
handleChange = event => {
this.props.dispatch({ type: "add" });
}
render = () => {
return (
<div>
<input
type="text"
onChange={this.handleChange} />
</div>
);
}
}
const mapStateToProps = state => ({ nameState: state.nameState});
export default connect(mapStateToProps)(Mycomponent);
nameAction.js
export const nameAction = () => ({
type: 'add'
});
export default { nameAction };
nameReducer.js
const nameReducer = (state = {}, action) => {
switch (action.type) {
case 'add': {
return {
...state,
nameState: action.payload
};
}
default:
return state;
}
};
export default nameReducer;
Outputcomponent.js
import React, { Component } from 'react';
class Outputcomponent extends Component {
render = (props) => {
return (
<div>
<div>{this.props.nameState }</div>
</div>
);
}
}
export default Outputcomponent;
The use of redux hooks explained by Josiah is for me the best approach but you can also use mapDispatchToProps.
Even if the main problem is that you don't pass any data in your 'add' action.
nameAction.js
You call the action.payload in nameReducer.js but it does not appear in your action
export const nameAction = (text) => ({
type: 'add',
payload: text
});
Mycomponent.js
Then as for your state we can mapDispatchToProps.
(I think it's better to trigger the action with a submit button and save the input change in your textInput state, but I guess it's intentional that there is none)
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {nameAction} from './nameAction'
class Mycomponent extends Component {
state = {
textInput: '',
}
handleChange = event => {
this.props.nameAction(event.target.value);
}
render = () => {
return (
<div>
<input
type="text"
onChange={this.handleChange} />
</div>
);
}
}
const mapStateToProps = state => ({ nameState: state.nameState});
const mapDispatchToProps = dispatch => ({ nameAction: (text) => dispatch(nameAction(text))});
export default connect(mapStateToProps,mapDispatchToProps)(Mycomponent);
OutputComponent.js
to get the data two possibilities either with a class using connect and mapStateToProps , or using the useSelector hook with a functional component.
with a Class
import React, { Component } from "react";
import { connect } from "react-redux";
class OutputComponent extends Component {
render = () => {
return (
<div>
<div>{this.props.nameState}</div>
</div>
);
};
}
const mapStateToProps = state => state;
export default connect(mapStateToProps)(OutputComponent);
with a functional component
import React from "react";
import { useSelector } from "react-redux";
const OutputComponent = () => {
const nameState = useSelector((state) => state.nameState);
return (
<div>
<div>{nameState}</div>
</div>
);
};
export default OutputComponent;
Of course you must not forget to create a strore and to provide it to the highest component
store.js
import { createStore } from "redux";
import nameReducer from "./nameReducer";
const store = createStore(nameReducer);
export default store;
index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { Provider } from "react-redux";
import store from "./store";
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
Component
const AddTodo = () => {
const [todo, setTodo] = useState("");
const dispatch = useDispatch();
const handleChange = (e) => setTodo(e.target.value);
const handleSubmit = (e) => {
e.preventDefault();
dispatch(addTodoAction(todo));
}
return {
<form onSubmit={handleSubmit}>
<input type="text" onChange={handleChange} />
</form>
}
)
Actions
const addTodoAction = (text) => {
dispatch({
type: "ADD_TODO",
payload: text
})
}
Reducers
const addTodoReducer = (state, action) => {
switch(action.type) {
case "ADD_TODO":
return {
todo: action.payload,
}
default:
return state;
}
}
store
// some code for store.js
Accessing this todo from another component
const ComponentA = () => {
const {todo} = useSelector(state => state.todo);
return (
<p> {todo} </p>
)
}
Side Note:
Redux comes with too much boilerplate if you want to pass text from one component to another, just use props
Hello am trying to refresh the graph after changing the value of select option but it shows the first graph and when I change the select option the state is changed but the graph didn't change I think the problem is in lifecycle component when the state changes didn't change only rendred for one time how can I fix it and thank you
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import Select from "react-select";
import Graph from "../graph/Graph";
class Home extends Component {
state = {
selectedOption: null
};
handleChange = selectedOption => {
this.setState({ selectedOption });
console.log(`Option selected:`, selectedOption);
};
render() {
const { user } = this.props.auth;
const { organization } = user;
console.log(organization);
//const organization = user.organization;
console.log(user);
//let organization = user.organization[0];
const options = organization.map(org => ({
value: org.conceptPrefix,
label: org.name
}));
const { selectedOption } = this.state;
let graphObject;
if (selectedOption == null) {
graphObject = <h4>Choose Organization</h4>;
} else {
graphObject = (
<div>
<Graph org={this.state.selectedOption.value} />
</div>
);
}
return (
<div>
<Select
value={selectedOption}
onChange={this.handleChange}
options={options}
/>
{graphObject}
</div>
);
}
}
Home.propTypes = {
auth: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
auth: state.auth,
graph: state.graph
});
export default connect(
mapStateToProps,
{}
)(Home);
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { graphGet } from "../../actions/graphActions";
import GraphImp from "./GraphImp";
class Graph extends Component {
constructor(props) {
super(props);
this.state = {
org: props.org
};
}
componentWillReceiveProps(nextProps) {
if (nextProps.errors) {
this.setState({ errors: nextProps.errors });
}
}
componentDidMount() {
this.props.graphGet(this.props.org);
}
render() {
// {this.props.graph.graph && this.state.formSubmitted
// ? this.createList()
// : "wait Graph"}
const { graph, loading } = this.props.graph;
let graphContent;
if (graph == null || loading) {
graphContent = <h4>Loading ...</h4>;
} else {
graphContent = <GraphImp grapheData={graph} />;
}
return <div>{graphContent}</div>;
}
}
Graph.prototypes = {
graphGet: PropTypes.func.isRequired,
auth: PropTypes.object.isRequired,
errors: PropTypes.object.isRequired,
graph: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
auth: state.auth,
graph: state.graph,
errors: state.errors
});
export default connect(
mapStateToProps,
{ graphGet }
)(Graph);
There are 2 ways to achieve your goal.
First option: Implement componentDidUpdate in Graph
componentDidUpdate(prevProps) {
if(prevProps.org !== this.props.org) {
this.setState({ org: this.props.org });
this.props.graphGet(this.props.org);
}
}
Second option: Force react to fully remount&render your graph whenever you change the option by changing the key (Make sure the key is not an object/array)
<Graph key={this.state.selectedOption.value} org={this.state.selectedOption.value} />
I have a component which is using redux connect. In this component I have mapStateToProps which getting project from redux state and projectTransform is a value which has filter values from project redux state:
import React, { Component } from 'react';
import PropTypes from "prop-types";
import { connect } from 'react-redux';
class ProjectForm extends Component {
constructor(props){
super(props);
}
componentDidMount() {
const {
fetchProject,
} = this.props;
fetchProject();
}
onClick() {
this.setState({
project1: {
"a": 1,
"b": 2
}
})
}
render() {
const { project1 } = this.props;
return (
<div>
<button onClick={onClick()} />
</div>
)
}
}
ProjectForm.propTypes = {
fetchProject: PropTypes.func.isRequired
};
function mapDispatchToProps (dispatch) {
return
fetchProject: () => dispatch(projectActions.getProjectRequest()),
}
}
function mapStateToProps ( state ) {
const { project} = state
return {
project: project,
project1: ((project) => {
return project[0]
})(project)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ProjectForm)
I trying to now trigger re-rendering on the button but I have not clue how to do it as I tried.
this.setState((previousState) => {
project1: [JSON value from Form]
});
Also why previousState is null I would assume it would have mapStateToProps data.
Any idea how to do it without dispatching whole redux? Or how to do it in a proper way?
The problem was with reading data not from the state but props.
render() {
const { project1 } = this.state;
return (
<div>
<button onClick={onClick()} />
</div>
)
}
I'm trying to create a search bar with React and redux. I have placed the search bar in one component (clientsearch.js) and the results in another (Clientdevicelocate.js)
I get a 'TypeError: Cannot read property 'map' of undefined' error on the line with the 'location.map'. It seems like I am not passing an initial state of "". I am a newbie to Redux and this is my first attempt at passing asynchronous code.
clientdevicelocate.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import './Clientdevicelocate.css';
import {Table, TableBody, TableHeader, TableHeaderColumn, TableRow, TableRowColumn,} from 'material-ui/Table';
class clientDevicelocate extends Component {
render () {
let locations = this.props.location;
console.log('====================================');
console.log('Locations', locations);
console.log('====================================');
return(
<div>
<Table>
<TableHeader>
<TableRow>
<TableHeaderColumn>Device Alias</TableHeaderColumn>
<TableHeaderColumn>Device Serial Number </TableHeaderColumn>
<TableHeaderColumn>Device IMEI</TableHeaderColumn>
<TableHeaderColumn>Locate Address</TableHeaderColumn>
<TableHeaderColumn>Locate Date</TableHeaderColumn>
<TableHeaderColumn>Locate Time</TableHeaderColumn>
</TableRow>
</TableHeader>
<TableBody>
{locations.map(function(location, i){
return <TableRow key={location.numVehicleDeviceID}>
<TableRowColumn>{location.txtAlias}</TableRowColumn>
<TableRowColumn>{location.txtSMSDeviceSN}</TableRowColumn>
<TableRowColumn>{location.IMEI}</TableRowColumn>
<TableRowColumn>{location.fullAddress}</TableRowColumn>
<TableRowColumn>{location.date}</TableRowColumn>
<TableRowColumn>{location.time}</TableRowColumn>
</TableRow>
})}
</TableBody>
</Table>
</div>
);
}
}
const mapStateToProps = state => {
return{
loactions: state.locations
};
}
export default connect(mapStateToProps)(clientDevicelocate);
clientReducer.js
import * as actionTypes from '../actions/actionTypes';
const intialState ={
client: {
numClientID:'',
txtName:''
}
};
const clientReducer = (state = intialState, action) => {
switch(action.type){
case actionTypes.SEARCH_CLIENT:
return{
...state,
client:{
...state.client,
}
};
default:
return state;
}
}
export default clientReducer;
searchClientAction
I cleared the API call line. The API works fine and we have been using it in postman and directly in clientdeviceloacte.js prior to moving the code to redux.
import * as actionTypes from './actionTypes';
import axios from 'axios';
export const setClient = (value) => {
return{
type: actionTypes.SEARCH_CLIENT,
client: value,
}
};
export const fetchClientFailed = () => {
return {
type: actionTypes.FETCH_CLIENT_FAILED
}
}
export const fetchClient = (value) => {
return dispatch => {
axios({
method: 'post',
url: 'http://.../.../getDeviceLocation',
data: {
"numClientID" : value,
"numContactID" : null,
"ynActive" : true
}
})
.then((response) => {
console.log(response)
let locations = response.data;
for(let i=0; i<locations.length; i++){
let location = locations[i];
locations[i].fullAddress = location.txtAddress + ', '+ location.txtCity + ', ' + location.txtState + ' ' +location.txtZip;
locations[i].date = location.dtDate.substring(0,10);
locations[i].time = location.dtDate.substring(11, 19);
}
dispatch(setClient(locations));
// this.setState({locations: locations});
})
.catch ( error => {
dispatch(fetchClientFailed());
});
}
};
Clientsearch.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { setClient} from '../../store/actions/indexAction';
class SearchBar extends Component {
render() {
const {fetchClient,value} = this.props;
return (
<input
className = 'form-control'
placeholder = 'Search Client'
onChange = {(e) => fetchClient(e.target.value)}
value = {value} />
);
}
}
const mapStateToProps = state => {
return {
value: state.locations
};
}
const mapDispatchToProps = dispatch => {
return bindActionCreators({setClient}, dispatch);
}
export default connect(mapStateToProps,mapDispatchToProps) (SearchBar);
You can try constructing an initial state of an empty array for locations in your clientDevicelocate component.
export default class clientDevicelocate extends Component{
constructor(props){
this.state = {
locations:[],
}
}
}
I needed to pass an empty array in the clientReducer.js in the initialState.
I have React with Redux and Electron project. I try to save current screen id to redux and get the saved state on next screen. The problem is, that when I use getSettings, the return value should be my saved state:
Object{settings: Object}
but is action's object:
Object{type: "GET_SETTINGS", payload: ""}
When I put console log to reducer_settings.js, it show correct state. So it seems it is something with binding the getSettings method. Thanks for your help
containers/screen_picker.js:
import React, {Component} from 'react';
import Navigation from "../components/navigation";
const {desktopCapturer, ipcRenderer} = require('electron');
import {connect} from 'react-redux';
const domify = require('domify')
import App from '../components/app'
import {bindActionCreators} from 'redux';
import {setSettings, getSettings} from "../actions/index";
class ScreenPicker extends App {
constructor(){
super();
this.showPicker();
}
showPicker(){
ipcRenderer.send('show-picker', { types: ['screen'] });
ipcRenderer.on('get-sources', (event, options) => {
desktopCapturer.getSources(options, (error, sources) => {
if (error) throw error
let sourcesList = document.querySelector('.capturer-list')
for (let source of sources) {
let thumb = source.thumbnail.toDataURL()
if (!thumb) continue
let title = source.name.slice(0, 20)
let item = `<li><img src="${thumb}"><span>${title}</span></li>`
sourcesList.appendChild(domify(item))
}
let links = sourcesList.querySelectorAll('a')
for (let i = 0; i < links.length; ++i) {
let closure = (i) => {
return (e) => {
e.preventDefault()
// ipcRenderer.send('source-id-selected', sources[i].id)
// sourcesList.innerHTML = ''
this.props.setSettings({
screenId: sources[i].id
});
}
}
links[i].onclick = closure(i)
}
})
})
}
render() {
return (
<div className="window-wrapper">
<div className="main-content">
<div className="capturer-container dn">
<div className="cr">
<p className="mbl">Select the window you want to share:</p>
<ul className="capturer-list"></ul>
</div>
</div>
</div>
<Navigation nextRouteUrl="/camera-test" backRouteUrl="/" />
</div>
)
}
}
function mapStateToProps(state){
return {
settings: state.settings
}
}
function mapDispatchToProps(dispatch){
return bindActionCreators({setSettings, getSettings}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(ScreenPicker);
containers/camera_test.js
const {ipcRenderer} = require('electron');
import React, {Component} from 'react';
import Navigation from "../components/navigation";
import {connect} from 'react-redux';
import App from '../components/app'
import {bindActionCreators} from 'redux';
import {getSettings} from "../actions/index";
class CameraTest extends App {
constructor(){
super();
}
componentDidMount() {
console.log("settings in camera test start");
console.log(this.props.getSettings());
console.log("settings in camera test end");
ipcRenderer.send('stepWindow:create', { });
}
render() {
return (
<div className="window-wrapper">
<div className="main-content">
CameraTest div
</div>
<Navigation nextRouteUrl="/camera-test" backRouteUrl="/screen-picker" />
</div>
)
}
}
function mapStateToProps(state){
return {
settings: state.settings
}
}
function mapDispatchToProps(dispatch){
return bindActionCreators({getSettings}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(CameraTest);
reducers/reducer_settings.js:
import {GET_SETTINGS, SET_SETTINGS} from "../actions/index";
export default function (state = {},action) {
let newState;
switch (action.type){
case GET_SETTINGS:
console.log("reducer GET_SETTINGS");
console.log(state);
return state;
case SET_SETTINGS:
newState = { ...state, ["settings"]: action.payload };
console.log("Start newstate");
console.log(newState);
console.log("End newstate");
return newState;
default:
return state
}
}
actions/index.js
export const SET_SETTINGS = 'SET_SETTINGS';
export const GET_SETTINGS = 'GET_SETTINGS';
export function setSettings(values, callback){
return {
type: SET_SETTINGS,
payload: values
}
}
export function getSettings(){
console.log("actions#getSettings");
return {
type: GET_SETTINGS,
payload: ""
}
}
you dont need the getSetting action creator.
in your component did mount access the settings like this.
componentDidMount() {
console.log("settings in camera test start");
const { settings} = this.props;
console.log(settings);
console.log("settings in camera test end");
ipcRenderer.send('stepWindow:create', { });
}
assuming your object is called settings. normally the object is given the name you are exporting in the reducer. so if you are not able to see an object called settings in props, you need to give your reducer function a name
export default function settings (state = {},action) {
let newState;
switch (action.type){
case GET_SETTINGS:
console.log("reducer GET_SETTINGS");
console.log(state);
return state;
case SET_SETTINGS:
newState = { ...state, ["settings"]: action.payload };
console.log("Start newstate");
console.log(newState);
console.log("End newstate");
return newState;
default:
return state
}
}
EDIT: its mapStateToProps which gives the object name is props.