I'm trying to connect the react-widgets DropdownList component to global redux store.
The DropdownList changes the value of the selected value if it is obtained from the this.state.
In the sample from the react-widgets docs in the onChange handler:
var DropdownList = ReactWidgets.DropdownList
, colors = ['orange', 'red', 'blue', 'purple'];
var Example = React.createClass({
getInitialState() {
return { value: colors[3] };
},
render() {
return (
<DropdownList
data={colors}
value={this.state.value}
onChange={value => this.setState({ value })}/>)
}
});
ReactDOM.render(<Example/>, mountNode);
But I want to set DropdownList value from the global redux store my codepen example:
const colors = [
{ id: 0, name: 'orange'},
{ id: 1, name: 'purple'},
{ id: 2, name: 'red' },
{ id: 3, name: 'blue' },
{ id: 4, name: 'green' }
];
class App extends React.Component {
componentWillMount() {
this.props.setSelectedColor(colors[1]); // !!! HELP, does not work !!!
}
render() {
return(
<DropdownList className="form-control form-control-sm"
id="color-list"
valueField="id"
textField="name"
data={colors}
value={this.props.selectedColor}
onChange={(value) => this.props.setSelectedColor(value)}
/>);
}
}
const reducers = {
list: setSelectedColor
};
// global redux state <-> store
const store = createStore(rootReducer);
// connect to global redux store selectedColor
function mapStateToProps (state) {
return {
selectedColor: state.selectedColor
}
}
// connect to global actions (setColor) to redux store
function mapDispatchToProps(dispatch) {
return {
setSelectedColor: bindActionCreators(setColor, dispatch)
}
}
const AppForm = connect(
mapStateToProps,
mapDispatchToProps
)(App);
ReactDOM.render(
<Provider store={store}>
<AppForm />
</Provider>,
document.getElementById('app')
);
In the method componentWillMount() I set the initial value list in the second element (purple) but it does not work!
What am I doing wrong?
I corrected the path connection state through reducer "list":
const reducers = {
list: setSelectedColor
};
function mapStateToProps (state) {
return {
selectedColor: state.list.selectedColor
}
}
Hooray, now it works!
Related
I am new to composition API with vue3. I have created that computed property and I would like to have that computed variable in a different file, I'm not sure if I should create a new component or I could achieve it from a js file.
Here is the component working (I did it with setup()):
export default {
name: "Recipes",
setup() {
const state = reactive({
recipes: [],
sortBy: "alphabetically",
ascending: true,
searchValue: "",
});
const favoritesRecipes = computed(() => {
let tempFavs = state.recipes;
// Show only favorites
if (state.heart) {
tempFavs = tempFavs.filter(item => {
return item.favorite;
});
}
return tempFavs;
...
});
...
}
return {
...toRefs(state),
favoriteRecipes
}
// end of setup
}
You can split it into two files
state.js
export const state = reactive({
recipes: [],
sortBy: "alphabetically",
ascending: true,
searchValue: "",
});
export const favoriteRecipes = computed(() => {
let tempFavs = state.recipes;
// Show only favorites
if (state.heart) {
tempFavs = tempFavs.filter(item => {
return item.favorite;
});
}
return tempFavs;
})
and recipes.vue
import { state, favoriteRecipes } from "state.js";
export default {
name: "Recipes",
setup() {
return {
...toRefs(state),
favoriteRecipes,
};
},
};
But this will make the state persistent, so if you have multiple components, they will all have the same favoriteRecipes and state values.
If you want them to be unique for each component...
state.js
export const withState = () => {
const state = reactive({
recipes: [],
sortBy: "alphabetically",
ascending: true,
searchValue: "",
});
const favoriteRecipes = computed(() => {
let tempFavs = state.recipes;
// Show only favorites
if (state.heart) {
tempFavs = tempFavs.filter((item) => {
return item.favorite;
});
}
return tempFavs;
});
return { state, favoriteRecipes };
};
and recipes.vue
import { withState } from "state.js";
export default {
name: "Recipes",
setup() {
const {state, favoriteRecipes} = withState()
return {
...toRefs(state),
favoriteRecipes,
};
},
};
I'm creating a simple react-redux chat application. I managed to display some dummy data from my redux state in my Message component. I succeed to push a new 'message' to the redux state from my Submit component. But the new item doesn´t render in the Message component.
So I tried to console log the previous state and the new state from the messageReducer and it seems to work. I get the state array with all the dummy data + the new pushed object.
Here is the Github repo if needed: https://github.com/MichalK98/Chat.V.2
// Message Component
import React, { Component } from 'react';
import { connect } from 'react-redux';
class Message extends Component {
render() {
return (
<ul id="chatroom">
{this.props.messages.map((msg) => (
<li className={(msg.username == 'You' ? "chat-me" : "")} key={msg.id}>
<p>{msg.message}</p>
<small>{msg.username}</small>
</li>
)).reverse()}
</ul>
)
}
}
const mapStateToProps = (state) => {
return {
messages: state.message.messages
}
}
export default connect(mapStateToProps)(Message);
// Submit Component
...
import { connect } from 'react-redux';
...
class Submit extends Component {
state = {
message: []
}
clear = async () => {
await this.setState({
message: ''
});
}
handleChange = async (e) => {
await this.setState({
message: e.target.value
});
}
handleSubmit = (e) => {
e.preventDefault();
this.props.writeMessage(this.state.message);
this.clear();
}
render() {
return (
<div className="chat-footer">
<form onSubmit={this.handleSubmit} autoComplete="off">
<input onChange={this.handleChange} value={this.state.message} type="text" placeholder="Skriv något..."/>
<button id="btnSend"><SendSvg/></button>
</form>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
messages: state.messages
}
}
const mapDispatchToProps = (dispatch) => {
return {
writeMessage: (message) => { dispatch({type: 'WRITE_MESSAGE', messages: {id: Math.random(), username: 'You', message: message}})}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Submit);
// messageReducer
const initState = {
messages: [
{id: 1, username: 'You', message: 'Hi, data from reducer!'},
{id: 2, username: 'Mattias', message: 'Wow..'},
{id: 3, username: 'Alien', message: 'Awesome!'}
]
}
const messageReducer = (state = initState, action) => {
if (action.type === 'WRITE_MESSAGE') {
state.messages.push(action.messages);
console.log('Action ',action.messages);
console.log('State ',state.messages);
}
return state;
}
export default messageReducer;
I expect that the new data will render in my Message component when I add a new object to the state array in messageReducer.
first of all in your Message Component you should Change mapStateToProps :
const mapStateToProps = (state) => {
return {
messages : state.messages
}
}
And then in your message reducer you should change reducer. this is better way for reducer. you shouldn't directly change state :
const messageReducer = (state = initState, action) => {
switch (action.type) {
case "WRITE_MESSAGE":
return { ...state, messages: [...state.messages, { ...action.messages }] }
default:
return state;
}
}
if you need any help i can help you to complete your project :-)
I'm trying to achieve infinite scrolling using ag grid react component, but it doesn't seems to be working.
here is my implementation :
import { AgGridReact } from 'ag-grid-react';
import 'ag-grid/dist/styles/ag-grid.css';
import 'ag-grid/dist/styles/ag-theme-balham.css';
class TasksGridContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: true,
gridOptions: {
//virtual row model
rowModelType: 'infinite',
paginationPageSize: 100,
cacheOverflowSize: 2,
maxConcurrentDatasourceRequests: 2,
infiniteInitialRowCount: 1,
maxBlocksInCache: 2,
components: {
loadingRenderer: function(params) {
console.log('loadingCellRenderer', params);
if (params.value !== undefined) {
return params.value;
} else {
return '<img src="https://raw.githubusercontent.com/ag-grid/ag-grid-docs/master/src/images/loading.gif">';
}
}
},
defaultColDef: {
editable: false,
enableRowGroup: true,
enablePivot: true,
enableValue: true
}
}
};
}
componentDidMount() {
this.props.actions.getAssignedTasks();
this.props.actions.getTeamTasks();
}
componentWillReceiveProps(newProps) {
if (this.props.taskView.taskGrid.listOfTasks.length > 0) {
this.setState({
loading: false ,
gridOptions: {
datasource: this.props.taskView.taskGrid.listOfTasks
}
});
}
}
render() {
return (
<div id="tasks-grid-container">
<div style={Style.agGrid} id="myGrid" className="ag-theme-balham">
<AgGridReact
columnDefs={this.props.taskView.taskGrid.myTaskColumns}
rowData={this.props.taskView.taskGrid.listOfTasks}
gridOptions={this.state.gridOptions}>
</AgGridReact>
</div>
</div>
);
}
}
TasksGridContainer.propTypes = {
listOfTasks: PropTypes.array,
actions: PropTypes.object
};
const mapStateToProps = ({ taskView }) => {
return {
taskView: {
taskGrid: {
listOfTasks: taskView.taskGrid.listOfTasks,
myTaskColumns: taskView.taskGrid.myTaskColumns,
teamTaskColumns: taskView.taskGrid.teamTaskColumns
}
}
}
};
const mapDispatchToProps = (dispatch) => {
return {
actions: bindActionCreators(taskGridActions, dispatch)
};
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(TasksGridContainer);
columnDefs are being set once props.taskView.taskGrid.myTaskColumns is available.
a sample columndef:
[
{
cellRenderer: "loadingRenderer", checkboxSelection: true, field: "action", headerCheckboxSelection: true, headerCheckboxSelectionFilteredOnly: true, headerName: "Action"
},
{
"activity"headerName: "Activity Name"
}
]
Although grid is loading fine, but when i scroll it should call "loadingRenderer" component. But,I'm not able to see any loading gif when i scroll.
Am i doing something wrong in implementation?
Actual issue was not calling the the props properly and was not having onGridReady function to use gridAPi.
I modified the code and it starts working:
<AgGridReact
components={this.state.components}
enableColResize={true}
rowBuffer={this.state.rowBuffer}
debug={true}
rowSelection={this.state.rowSelection}
rowDeselection={true}
rowModelType={this.state.rowModelType}
paginationPageSize={this.state.paginationPageSize}
cacheOverflowSize={this.state.cacheOverflowSize}
maxConcurrentDatasourceRequests={this.state.maxConcurrentDatasourceRequests}
infiniteInitialRowCount={this.state.infiniteInitialRowCount}
maxBlocksInCache={this.state.maxBlocksInCache}
columnDefs={this.props.columns}
rowData={this.props.rowData}
onGridReady={this.onGridReady}
>
</AgGridReact>
state :
this.state = {
components: {
loadingRenderer: function(params) {
if (params.value !== undefined) {
return params.data.action;
} else {
return '<img src="https://raw.githubusercontent.com/ag-grid/ag-grid-docs/master/src/images/loading.gif">';
}
}
},
rowBuffer: 0,
rowSelection: "multiple",
rowModelType: "infinite",
paginationPageSize: 100,
cacheOverflowSize: 2,
maxConcurrentDatasourceRequests: 2,
infiniteInitialRowCount: 1,
maxBlocksInCache: 2
};
onGridReady function :
onGridReady = (params, data = []) => {
this.gridApi = params;
this.gridColumnApi = params.columnApi;
this.updateData(params,data);
}
I have several a stackNavigator nested in a tabNavigator and this tabNavigator is nested in a stackNavigator. I have integrated redux in order to navigate easily.
My navigators :
const StackA = StackNavigator({
Main: { screen: ScreenA }
})
const TabBarNavigator = TabNavigator({
Home: { screen: StackA },
Profil: { screen: ProfilScreen }
})
const AppNavigator = StackNavigator({
Tabbar: { screen: TabBarNavigator },
Other: { screen: OtherScreen }
})
const AppWithNavigationState = ({ dispatch, nav }) => (
<AppNavigator navigation={addNavigationHelpers({ dispatch, state: nav
})} />
);
AppWithNavigationState.propTypes = {
dispatch: PropTypes.func.isRequired,
nav: PropTypes.object.isRequired,
};
const mapStateToProps = state => ({
nav: state.appNav,
});
export default connect(mapStateToProps)(AppWithNavigationState);
When I set the initial state in redux i have an error :
There is no route defined for key Tabbar. Must be one of :'StackA'
My nav reducer :
const initialNavState =
AppNavigator.router.getStateForAction(NavigationActions.reset({
index: 0,
actions: [
NavigationActions.navigate({
routeName: 'Tabbar',
})
],
}));
function nav(state = initialNavState, action) {
let nextState;
switch (action.type) {
case actions.types.navigate:
const { routeName } = action;
nextState = AppNavigator.router.getStateForAction(
NavigationActions.navigate({ routeName }),
state
);
break;
case 'Logout':
nextState = AppNavigator.router.getStateForAction(
NavigationActions.navigate({ routeName: 'Login' }),
state
);
break;
default:
nextState = AppNavigator.router.getStateForAction(action, state);
break;
}
return nextState || state;
}
I understand in my StackA i don't have Tabbar route but i do in my appNavigator. I want to initialise my default route to Tabbar in my AppNavigator without affected my StackA. It's possible ? if yes, can i use this navigation for my StackA ?
I have list of items on single screen. The list is fetched from redux store and rendered as set of form components. At the bottom of list I have "Add new" button.
In my initial implementation I have used container component with setState({newItem: {...}). This has side effect of view flickering when newItem is cleared and new item is added to redux items.
{items.map((item, index) =>
<ItemForm
key={index}
formKey={index.toString()}
initialValues={item}
submitItem={this.handleUpdateItem.bind(this)}
removeItem={() => this.handleRemoveItem(item.id)}
/>
)}
{newItem &&
<ItemForm
key="new"
formKey="new"
initialValues={newItem}
submitItem={this.handleCreateItem.bind(this)}
removeItem={() => this.handleRemoveNewItem()}
/>
}
I think better way to implement this would be not to use newItem and instead handle the new item inside redux. But in that case I have to change the actions/reducers API, in particular the CREATE_ITEM would no longer append item but rather updates the last item in store.
Is there some way I can reduce the flickering or do you have some suggestions how to do it with redux? In particular - what set of actions / reducers should I use to model better solution? Should I rather make API calls in container and simplify the redux actions?
The complexity of the actions/reducers API for me comes from mixing persisted (requires id to make request) and not persisted objects (requires index to find) in redux.
Here is my redux module:
import axios from 'axios';
import * as _ from 'lodash';
export const FETCH_ITEMS = 'items/FETCH_ITEMS';
export const CREATE_ITEM = 'items/CREATE_ITEM';
export const REMOVE_ITEM = 'items/REMOVE_ITEM';
export const UPDATE_ITEM = 'items/UPDATE_ITEM';
const initialState = {
items: []
};
export default function reducer(state = initialState, action) {
let index;
switch (action.type) {
case FETCH_ITEMS:
return {
...state,
items: action.items
};
case CREATE_ITEM: // would become "last item UPDATE_ITEM"
return {
...state,
items: [
...state.items,
action.item
]
};
case REMOVE_ITEM:
index = _.findIndex(state.items, {id: action.id});
return {
...state,
items: [
...state.items.slice(0, index),
action.item,
...state.items.slice(index + 1)
]
};
case UPDATE_ITEM:
index = _.findIndex(state.items, {id: action.item.id});
return {
...state,
items: [
...state.items.slice(0, index),
action.item,
...state.items.slice(index + 1)
]
};
default:
return state;
}
}
export function fetchItems() {
return function (dispatch) {
return axios.get('/api/items')
.then(res => {
const items = res.data;
dispatch({type: FETCH_ITEMS, items});
})
}
}
export function createItem(item) {
return function (dispatch) {
return axios.post('/api/items', item)
.then(res => {
const item = res.data.item;
dispatch({type: CREATE_ITEM, item});
})
}
}
export function removeItem(id) {
return function (dispatch) {
return axios.delete(`/api/items/${id}`)
.then(() => {
dispatch({type: REMOVE_ITEM, id});
});
}
}
export function updateItem(item) {
const {id, ...rest} = item;
return function (dispatch) {
return axios.put(`/api/items/${id}`, rest)
.then(res => {
const item = deserializeItem(res.data.item);
dispatch({type: UPDATE_ITEM, item});
})
}
}